package com.wzg.ftpstresstool.service;

import com.wzg.ftpstresstool.config.FileGeneratorProperties;
import com.wzg.ftpstresstool.config.FtpProperties;
import com.wzg.ftpstresstool.model.RecordFileEntry;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class FileReceiverService {
    
    @Autowired
    private FileGeneratorProperties fileGeneratorProperties;
    
    @Autowired
    private FtpProperties ftpProperties;
    
    // 用于存储记录文件名
    private String recordFileName;
    
    /**
     * 接收文件的主要方法（接收方模式）
     */
    public void receiveFiles() {
        log.info("开始执行接收方逻辑");
        
        try {
            // 确保目录存在
            ensureDirectoriesExist();
            
            // 循环访问FTP，直到下载了记录文件
            boolean recordFileDownloaded = false;
            int retryCount = 0;
            final int maxRetries = fileGeneratorProperties.getMaxRetries(); // 使用配置文件中的值
            final long retryInterval = fileGeneratorProperties.getRetryInterval(); // 使用配置文件中的值
            
            while (!recordFileDownloaded && retryCount < maxRetries) {
                log.info("第{}次尝试从FTP下载文件", retryCount + 1);
                
                // 从FTP下载文件
                recordFileDownloaded = downloadFilesFromFtp();
                
                if (!recordFileDownloaded) {
                    retryCount++;
                    if (retryCount < maxRetries) {
                        log.info("未找到记录文件，{}秒后进行下一次尝试", retryInterval / 1000);
                        Thread.sleep(retryInterval);
                    }
                }
            }
            
            if (recordFileDownloaded) {
                log.info("已成功下载记录文件，认为FTP取文件完成");
                
                // 解析记录文件
                List<RecordFileEntry> entries = parseRecordFile();
                
                // 校验文件
                validateFiles(entries);
                
                // 更新记录文件
                updateRecordFile(entries);
                
            } else {
                log.warn("经过{}次尝试仍未找到记录文件，结束接收流程", maxRetries);
            }
            
        } catch (Exception e) {
            log.error("接收文件时发生错误", e);
        }
        
        log.info("接收方逻辑执行完成");
    }
    
    /**
     * 确保必要的目录存在
     */
    private void ensureDirectoriesExist() throws IOException {
        // 创建生成目录
        Path generatePath = Paths.get(fileGeneratorProperties.getDirectory());
        if (!Files.exists(generatePath)) {
            Files.createDirectories(generatePath);
        }

        // 创建接收目录
        Path receivePath = Paths.get(fileGeneratorProperties.getReceiveDirectory());
        if (!Files.exists(receivePath)) {
            Files.createDirectories(receivePath);
        }
        // 如果接收目录下已经有文件，将这些文件全部删除
        List<Path> files = Files.list(receivePath)
                .filter(Files::isRegularFile)
                .collect(Collectors.toList()); 
        for (Path file : files) {
            Files.delete(file);
        }
        log.info("已删除接收目录下的旧文件: {}", 0);
        
        // 创建记录文件目录
        Path recordPath = Paths.get(fileGeneratorProperties.getRecordDirectory());
        if (!Files.exists(recordPath)) {
            Files.createDirectories(recordPath);
        }
    }
    
    /**
     * 从FTP下载文件到本地目录
     * @return 是否成功下载到记录文件
     */
    private boolean downloadFilesFromFtp() {
        FTPClient ftpClient = new FTPClient();
        boolean recordFileDownloaded = false;
        
        try {
            // 连接FTP服务器
            ftpClient.connect(ftpProperties.getServer(), ftpProperties.getPort());
            ftpClient.login(ftpProperties.getUsername(), ftpProperties.getPassword());
            ftpClient.enterLocalPassiveMode();
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            
            log.info("成功连接到FTP服务器 {}:{}", ftpProperties.getServer(), ftpProperties.getPort());
            
            // 列出FTP目录中的所有文件
            String uploadDirectory = ftpProperties.getUploadDirectory();
            FTPFile[] ftpFiles;
            
            // 如果uploadDirectory不为空，则在指定目录下列出文件，否则在根目录下列出文件
            if (uploadDirectory != null && !uploadDirectory.trim().isEmpty()) {
                ftpFiles = ftpClient.listFiles(uploadDirectory);
                log.info("在FTP目录 {} 中找到 {} 个文件", uploadDirectory, ftpFiles.length);
            } else {
                ftpFiles = ftpClient.listFiles();
                log.info("在FTP根目录中找到 {} 个文件", ftpFiles.length);
            }

            //检查receiveDirectory是否存在，不存在则创建
            Path receivePath = Paths.get(fileGeneratorProperties.getReceiveDirectory());
            if (!Files.exists(receivePath)) {
                Files.createDirectories(receivePath);
            }
            
            // 下载每个文件到本地目录
            for (FTPFile ftpFile : ftpFiles) {
                if (ftpFile.isFile()) {
                    String fileName = ftpFile.getName();
                    String remoteFilePath;
                    // 如果uploadDirectory不为空，则包含目录路径，否则直接使用文件名
                    if (uploadDirectory != null && !uploadDirectory.trim().isEmpty()) {
                        remoteFilePath = uploadDirectory + "/" + fileName;
                    } else {
                        remoteFilePath = fileName;
                    }
                    
                    String localFilePath = Paths.get(fileGeneratorProperties.getReceiveDirectory(), fileName).toString();
                    
                    // 检查是否为记录文件
                    if (fileName.startsWith(fileGeneratorProperties.getRecordPrefix())) {
                        recordFileDownloaded = true;
                        this.recordFileName = fileName;
                        log.info("检测到记录文件: {}", fileName);
                    }
                    
                    try (FileOutputStream fos = new FileOutputStream(localFilePath)) {
                        boolean success = ftpClient.retrieveFile(remoteFilePath, fos);
                        if (success) {
                            log.info("成功下载文件: {}", fileName);
                            // 下载成功后删除FTP服务器上的文件，实现剪切操作
                            boolean deleted = ftpClient.deleteFile(remoteFilePath);
                            if (deleted) {
                                log.info("已从FTP服务器删除文件: {}", fileName);
                            } else {
                                log.warn("无法从FTP服务器删除文件: {}", fileName);
                            }
                        } else {
                            log.error("下载文件失败: {}", fileName);
                        }
                    }
                }
            }
            
        } catch (IOException e) {
            log.error("FTP下载过程中发生错误", e);
        } finally {
            try {
                if (ftpClient.isConnected()) {
                    ftpClient.logout();
                    ftpClient.disconnect();
                }
            } catch (IOException ex) {
                log.error("断开FTP连接时发生错误", ex);
            }
        }
        
        return recordFileDownloaded;
    }
    
    /**
     * 解析记录文件
     * @return 记录文件条目列表
     */
    private List<RecordFileEntry> parseRecordFile() throws IOException {
        List<RecordFileEntry> entries = new ArrayList<>();
        String recordFilePath = Paths.get(fileGeneratorProperties.getReceiveDirectory(), recordFileName).toString();
        
        try (BufferedReader reader = new BufferedReader(new FileReader(recordFilePath))) {
            String line;
            boolean dataStarted = false;
            
            while ((line = reader.readLine()) != null) {
                // 跳过文件头，直到数据行开始
                if (!dataStarted) {
                    if (line.startsWith("ID")) {
                        dataStarted = true;
                    }
                    continue;
                }
                
                // 解析数据行
                String[] parts = line.split("\t");
                if (parts.length >= 7) {
                    RecordFileEntry entry = new RecordFileEntry();
                    entry.setId(Long.valueOf(parts[0]));
                    entry.setFileName(parts[1]);
                    entry.setSize(Long.valueOf(parts[2]));
                    entry.setCreateTime(LocalDateTime.parse(parts[3]));
                    entry.setMd5(parts[4]);
                    entry.setUploadStatus(parts[5]);
                    entry.setUploadTime(parts[6]);
                    entries.add(entry);
                }
            }
        }
        
        log.info("解析记录文件完成，共{}条记录", entries.size());
        return entries;
    }
    
    /**
     * 校验文件
     * @param entries 记录文件条目列表
     */
    private void validateFiles(List<RecordFileEntry> entries) {
        log.info("开始校验文件");
        
        for (RecordFileEntry entry : entries) {
            String localFilePath = Paths.get(fileGeneratorProperties.getDirectory(), entry.getFileName()).toString();
            File file = new File(localFilePath);
            
            // 1. 检查文件是否已经下载
            if (file.exists()) {
                entry.setDownloaded(true);
                entry.setMissing(false);
                // 记录下载完成时间，取文件的系统生成时间
                try {
                    entry.setDownloadCompleteTime(LocalDateTime.ofInstant(Files.getLastModifiedTime(file.toPath()).toInstant(), ZoneId.systemDefault()));
                } catch (IOException e) {
                    log.warn("无法获取文件的最后修改时间: {}", entry.getFileName(), e);
                    entry.setDownloadCompleteTime(LocalDateTime.now());
                }
                log.debug("文件已下载: {}", entry.getFileName());
                
                // 2. 检查文件名中记录的md5与实际收到的文件的md5值是否一致
                try {
                    String actualMd5 = calculateMD5(file);
                    if (actualMd5.equals(entry.getMd5())) {
                        entry.setMd5CheckResult("一致");
                    } else {
                        entry.setMd5CheckResult("不一致");
                    }
                } catch (IOException e) {
                    log.error("计算文件MD5时出错: {}", entry.getFileName(), e);
                    entry.setMd5CheckResult("计算失败");
                }
                
                // 3. 检查文件名记录的文件时间，和文件保存的时间的间隔
                try {
                    // 从文件名中提取时间
                    String fileTimeStr = extractTimeFromFileName(entry.getFileName());
                    LocalDateTime fileTime = parseFileTime(fileTimeStr);
                    LocalDateTime downloadTime = entry.getDownloadCompleteTime(); // 使用记录的下载完成时间
                    
                    // 计算时间差（毫秒）
                    long timeDiff = java.time.Duration.between(fileTime, downloadTime).toMillis();
                    entry.setTimeDifference(timeDiff + "ms");
                } catch (Exception e) {
                    log.error("计算时间差时出错: {}", entry.getFileName(), e);
                    entry.setTimeDifference("计算失败");
                }
            } else {
                entry.setDownloaded(false);
                entry.setMissing(true);
                log.warn("文件未下载: {}", entry.getFileName());
            }
        }
        
        log.info("文件校验完成");
    }
    
    /**
     * 从文件名中提取时间字符串
     * @param fileName 文件名
     * @return 时间字符串
     */
    private String extractTimeFromFileName(String fileName) {
        // 文件名格式: prefix_id_previousId_YYYYMMDD_HHmmss_SSS_md5.txt
        String[] parts = fileName.split("_");
        if (parts.length >= 7) {
            // 组合时间部分: YYYYMMDD_HHmmss_SSS
            return parts[parts.length - 4] + "_" + parts[parts.length - 3] + "_" + parts[parts.length - 2];
        }
        return "";
    }
    
    /**
     * 解析文件时间
     * @param timeStr 时间字符串
     * @return LocalDateTime对象
     */
    private LocalDateTime parseFileTime(String timeStr) {
        try {
            return LocalDateTime.parse(timeStr, DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_SSS"));
        } catch (DateTimeParseException e) {
            log.error("解析文件时间失败: {}", timeStr, e);
            return LocalDateTime.now();
        }
    }
    
    /**
     * 更新记录文件，添加校验结果
     * @param entries 记录文件条目列表
     */
    private void updateRecordFile(List<RecordFileEntry> entries) throws IOException {
        // 原记录文件路径
        String recordFilePath = Paths.get(fileGeneratorProperties.getReceiveDirectory(), recordFileName).toString();
        // 带_result后缀的结果文件路径
        String resultRecordFilePath = recordFilePath.replaceFirst("\\.txt$", "_result.txt");
        
        try (PrintWriter writer = new PrintWriter(new FileWriter(resultRecordFilePath))) {
            // 写入文件头
            writer.println("文件生成记录");
            writer.println("生成时间: " + LocalDateTime.now());
            writer.println("==========================================");
            
            // 写入列标题，添加新的校验列
            writer.println("ID\t文件名\t大小(字节)\t创建时间\tMD5\t上传状态\t上传时间\t是否下载\t下载完成时间\tMD5校验结果\t时间差\t是否遗漏");
            
            // 写入数据行
            for (RecordFileEntry entry : entries) {
                writer.printf("%d\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s%n",
                        entry.getId(),
                        entry.getFileName(),
                        entry.getSize(),
                        entry.getCreateTime(),
                        entry.getMd5(),
                        entry.getUploadStatus(),
                        entry.getUploadTime(),
                        entry.isDownloaded() ? "是" : "否",
                        entry.getDownloadCompleteTime() != null ? entry.getDownloadCompleteTime().toString() : "N/A",
                        entry.getMd5CheckResult(),
                        entry.getTimeDifference(),
                        entry.isMissing() ? "是" : "否");
            }
        }
        
        log.info("记录文件更新完成，结果保存在: {}", resultRecordFilePath);
    }
    
    /**
     * 计算文件的MD5值
     */
    private String calculateMD5(File file) throws IOException {
        try (FileInputStream fis = new FileInputStream(file)) {
            return DigestUtils.md5DigestAsHex(fis);
        }
    }
}